﻿using System;
using System.Data;
using System.Globalization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Practical.SharePoint.Base
{
    public static class SharePointSecurity
    {
        //need to catch all exceptions in this method
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public static void AnonymousSafeRunWithElevatedPrivileges(SPSite spSiteToElevate, SPWeb spWebToElevate, Action<SPSite, SPWeb> codeToRunElevated)
        {
            if (codeToRunElevated == null)
            {
                throw new ArgumentNullException("codeToRunElevated");
            }
            try
            {
                if (spSiteToElevate != null
                    &&
                    spWebToElevate != null
                    )
                {
                    Guid currentSiteId = spSiteToElevate.ID;
                    Guid currentWebId = spWebToElevate.ID;

                    SPUrlZone currentZone = spSiteToElevate.Zone;
                    SPUserToken systemAccount = SPUserToken.SystemAccount;

                    using (var spSiteElevated = new SPSite(currentSiteId, currentZone, systemAccount))
                    {
                        using (var spWebElevated = spSiteElevated.OpenWeb(currentWebId))
                        {
                            try
                            {
                                codeToRunElevated(spSiteElevated, spWebElevated);
                            }
                            catch (UnauthorizedAccessException ex)
                            {
                                PrefixTrace.WriteLine(System.Reflection.Assembly.GetCallingAssembly() + ex.ToString());
                            }
                        }
                    }
                }
            }
            catch (System.Data.SqlClient.SqlException sqlEx)
            {
                    PrefixTrace.WriteLine(
                        string.Format(
                            CultureInfo.InvariantCulture,
                            "{0}",
                            sqlEx
                            ));

                    if (spSiteToElevate != null)
                    {
                        PrefixTrace.WriteLine(
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "Most likely no access to site collection {0} via web application database {1} by current application pool identity {2}",
                                spSiteToElevate.Url,
                                spSiteToElevate.ContentDatabase.Name,
                                SPContext.Current.Site.WebApplication.ApplicationPool.ManagedAccount.Username
                                ));
                    }
            }
            catch (Exception ex)
            {
                PrefixTrace.WriteLine(System.Reflection.Assembly.GetCallingAssembly() + " " + ex);
            }
        }

        public static void AnonymousSafeRunWithElevatedPrivileges(Guid spSiteToElevateId, Guid spWebToElevateId, Action<SPSite, SPWeb> codeToRunElevated)
        {
            if (codeToRunElevated == null)
            {
                throw new ArgumentNullException("codeToRunElevated");
            }
            try
            {
                SPUserToken systemAccount = SPUserToken.SystemAccount;

                using (var spSiteElevated = new SPSite(spSiteToElevateId, systemAccount))
                {
                    using (var spWebElevated = spSiteElevated.OpenWeb(spWebToElevateId))
                    {
                        try
                        {
                            codeToRunElevated(spSiteElevated, spWebElevated);
                        }
                        catch (UnauthorizedAccessException ex)
                        {
                            PrefixTrace.WriteLine(System.Reflection.Assembly.GetCallingAssembly() + ex.ToString());
                        }
                    }
                }
            }
            catch (SPException ex)
            {
                PrefixTrace.WriteLine(System.Reflection.Assembly.GetCallingAssembly() + ex.ToString());
            }
        }

        public static bool IsUserInGroup(this SPPrincipal spPrincipal, SPUser targetUser)
        {
            bool containsUser = false;
            if (spPrincipal != null 
                &&
                targetUser != null)
            {
                using (SPSite targetSite = new SPSite(spPrincipal.ParentWeb.Site.ID, targetUser.UserToken))
                {
                    using (SPWeb targetWeb = targetSite.OpenWeb(spPrincipal.ParentWeb.ID))
                    {
                        try
                        {
                            containsUser = targetWeb.SiteGroups[spPrincipal.Name].ContainsCurrentUser;
                        }
                        catch (SPException)
                        {
                            return false;
                        }
                    }
                }
            }
            return containsUser;
        }

        /// <summary>Returns an elevated site</summary>
        /// <param name="spSite">
        /// The site that you want an elevated instance of. 
        /// You must dispose of this object unless it is part of SPContext.Current.
        /// </param>
        /// <exception cref="NullReferenceException"></exception>
        /// <returns>An elevated site context.</returns>
        /// <remarks>Be sure to dispose of objects created from this method.</remarks>
        public static SPSite GetElevatedSite(SPSite spSite)
        {
            if (spSite == null)
            {
                throw new NoNullAllowedException("spSite can not be null");
            }
            var sysToken = GetSystemToken(spSite);
            return new SPSite(spSite.ID, sysToken);
        }

        /// <summary>Gets a UserToken for the system account.</summary>
        /// <param name="site"></param> 
        /// <returns>A usertoken for the system account user.</returns>
        /// <remarks>Use this token to impersonate the system account</remarks>
        private static SPUserToken GetSystemToken(SPSite site)
        {
            site.CatchAccessDeniedException = false;
            try
            {
                return site.SystemAccount.UserToken;
            }
            catch (UnauthorizedAccessException)
            {
                SPUserToken sysToken = null;

                // Only use runwithelevated to grab the system user token.
                SPSecurity.RunWithElevatedPrivileges(
                    delegate
                        {
                            using (SPSite internalSite = new SPSite(site.ID))
                            {
                                sysToken = internalSite.SystemAccount.UserToken;
                            }
                        }
                    );
                return sysToken;
            }
        }
    }
}
